/**
 * Copyright &copy; 2020 <a href="https://github.com/somowhere/exam-system">exam-system</a> All rights reserved.
 */
package com.shanhesoft.java.modules.exam.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.shanhesoft.java.common.core.config.ApplicationConfig;
import com.shanhesoft.java.common.core.constant.CommonConstants;
import com.shanhesoft.java.common.core.util.FileUtil;
import com.shanhesoft.java.common.core.util.ObjectUtil;
import com.shanhesoft.java.common.core.util.Result;
import com.shanhesoft.java.common.core.util.StringUtil;
import com.shanhesoft.java.common.core.vo.PageModel;
import com.shanhesoft.java.common.persistence.service.impl.DataServiceImpl;
import com.shanhesoft.java.common.rancher.type.Container;
import com.shanhesoft.java.modules.exam.domain.TVirtualTrainingEnvironment;
import com.shanhesoft.java.modules.exam.domain.VirtualTrainingDetailInstance;
import com.shanhesoft.java.modules.exam.domain.dto.VirtualTrainingDetailInstanceDto;
import com.shanhesoft.java.modules.exam.domain.dto.VirtualTrainingDetailInstanceQueryCriteria;
import com.shanhesoft.java.modules.exam.domain.vo.BankVirtualTrainingQueVo;
import com.shanhesoft.java.modules.exam.domain.vo.VirtualTrainingDetailInstanceVo;
import com.shanhesoft.java.modules.exam.repository.VirtualTrainingDetailInstanceRepository;
import com.shanhesoft.java.modules.exam.service.BankVirtualTrainingQueService;
import com.shanhesoft.java.modules.exam.service.TVirtualTrainingEnvironmentService;
import com.shanhesoft.java.modules.exam.service.VirtualTrainingDetailInstanceService;
import com.shanhesoft.java.modules.exam.util.PropertiesUtils;
import com.shanhesoft.java.modules.sys.domain.Dict;
import com.shanhesoft.java.modules.sys.domain.dto.ContainerDTO;
import com.shanhesoft.java.modules.sys.domain.vo.UserVo;
import com.shanhesoft.java.modules.sys.service.DictService;
import com.shanhesoft.java.modules.sys.service.UserService;
import io.undertow.connector.ByteBufferPool;
import io.undertow.server.DefaultByteBufferPool;
import io.undertow.websockets.client.WebSocketClient;
import io.undertow.websockets.core.WebSocketChannel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.xnio.IoFuture;
import org.xnio.OptionMap;
import org.xnio.Xnio;
import org.xnio.XnioWorker;

import java.net.URI;
import java.nio.charset.Charset;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
 * 课程实训题目实例管理ServiceImpl 课程实训题目实例管理
 * @author admin
 * @version 2020-11-17 01:04:34
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class VirtualTrainingDetailInstanceServiceImpl extends DataServiceImpl<VirtualTrainingDetailInstanceRepository, VirtualTrainingDetailInstance, VirtualTrainingDetailInstanceDto, String> implements VirtualTrainingDetailInstanceService{

	@Autowired
	private RancherService rancherService;
	@Autowired
	private BankVirtualTrainingQueService bankVirtualTrainingQueService;
	@Autowired
	private UserService userService;
	@Autowired
	private TVirtualTrainingEnvironmentService virtualTrainingEnvironmentService;
	@Autowired
	private DictService dictService;

	@Override
	public void saveOrUpdate(VirtualTrainingDetailInstanceDto virtualTrainingDetailInstanceDto) {
		super.saveOrUpdate(virtualTrainingDetailInstanceDto);
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public VirtualTrainingDetailInstanceDto saveOrUpdateReturn(VirtualTrainingDetailInstanceDto virtualTrainingDetailInstanceDto) {
		super.saveOrUpdate(virtualTrainingDetailInstanceDto);

		return virtualTrainingDetailInstanceDto;
	}

	@Override
	public IPage<VirtualTrainingDetailInstanceVo> findPage(PageModel pm, QueryWrapper wrapper) {
		return repository.findTrainingDetailInstanceVoPage(pm, wrapper);
	}

	@Override
	public VirtualTrainingDetailInstanceVo findVoById(String id) {
		return repository.findTrainingDetailInstanceVoById(id);
	}

	@Override
	public IPage<Map<String, Object>> getUserList(PageModel pm, VirtualTrainingDetailInstanceQueryCriteria virtualTrainingDetailInstanceQueryCriteria) {
		return repository.findTrainingInstanceUserVoById(pm);
	}

	@Override
	public Result startVirtualEnv(VirtualTrainingDetailInstanceDto virtualTrainingDetailInstanceDto) {
		if(ObjectUtil.isEmpty(virtualTrainingDetailInstanceDto)
				|| StringUtil.isEmpty(virtualTrainingDetailInstanceDto.getTrainQueId())
				|| StringUtil.isEmpty(virtualTrainingDetailInstanceDto.getUserId())) {
			return Result.buildFail("参数不能为空");
		}
		BankVirtualTrainingQueVo bankVirtualTrainingQueVo = bankVirtualTrainingQueService.findVoById(virtualTrainingDetailInstanceDto.getTrainQueId());
		if(ObjectUtil.isEmpty(bankVirtualTrainingQueVo)) {
			return Result.buildFail("实训题目不存在！");
		}

		String answer = bankVirtualTrainingQueVo.getAnswer();
		String material = bankVirtualTrainingQueVo.getMaterial();
		String computingResource = bankVirtualTrainingQueVo.getComputingResource();
		String trainingEnvironmentId = bankVirtualTrainingQueVo.getTrainingEnvironmentId();
		TVirtualTrainingEnvironment virtualTrainingEnvironment = virtualTrainingEnvironmentService.getById(trainingEnvironmentId);
		Integer runTime = bankVirtualTrainingQueVo.getRunTime();
		virtualTrainingDetailInstanceDto.setRemainingTime(runTime);
		UserVo userVo = userService.findUserVoById(virtualTrainingDetailInstanceDto.getUserId());
		String roleNames = userVo.getRoleNames();

		QueryWrapper<VirtualTrainingDetailInstance> queryWrapper = new QueryWrapper<>();
		queryWrapper.eq("train_que_id", virtualTrainingDetailInstanceDto.getTrainQueId());
		queryWrapper.eq("user_id", virtualTrainingDetailInstanceDto.getUserId());
		VirtualTrainingDetailInstance trainingDetailInstance = repository.selectOne(queryWrapper);
		if(ObjectUtil.isNotEmpty(trainingDetailInstance) && !"removed".equals(trainingDetailInstance.getContainerState())
				&& !"purged".equals(trainingDetailInstance.getContainerState())
				&& StringUtil.isNotEmpty(trainingDetailInstance.getContainerId())
				&& rancherService.isContainerExistById(trainingDetailInstance.getContainerId()).getData()) {
			Result<Container> container = rancherService.startContainer(trainingDetailInstance.getContainerId(), trainingDetailInstance.getRemainingTime());
			if(CommonConstants.FAIL.intValue() == container.getCode()) {
				virtualTrainingDetailInstanceDto.setId(trainingDetailInstance.getId());
				return createEnv(virtualTrainingDetailInstanceDto, answer, material, computingResource, virtualTrainingEnvironment,
						runTime, roleNames, trainingDetailInstance.getId());
			}
			trainingDetailInstance.setContainerId(container.getData().getId());
			trainingDetailInstance.setContainerStartTime(LocalDateTime.now());
			trainingDetailInstance.setContainerState(container.getData().getState());
			String containerIp = container.getData().getPrimaryIpAddress();
			trainingDetailInstance.setContainerIp(containerIp);
			saveOrUpdate(trainingDetailInstance);
			Map<String, Object> result = BeanUtil.beanToMap(trainingDetailInstance);
//			result.put("remainingTime", trainingDetailInstance.getRemainingTime());
			result.put("envType", virtualTrainingEnvironment.getEnvType());
			result.put("container", containerIp+":"+virtualTrainingEnvironment.getPort());
			result.put("token", virtualTrainingEnvironment.getToken());
			waitForEnvStart(containerIp, virtualTrainingEnvironment.getPort().toString(), virtualTrainingEnvironment.getToken(), virtualTrainingEnvironment.getEnvType());
			return Result.buildOkData(result, "启动环境成功！");
		}

		if(ObjectUtil.isEmpty(trainingDetailInstance)) {
			return createEnv(virtualTrainingDetailInstanceDto, answer, material, computingResource, virtualTrainingEnvironment,
					runTime, roleNames, null);
		}else {
			String containerId = trainingDetailInstance.getContainerId();
			virtualTrainingDetailInstanceDto.setId(trainingDetailInstance.getId());
			if(StringUtil.isNotEmpty(containerId)
					&& rancherService.isContainerExistById(containerId).getData()) {
				Result<Container> container = rancherService.startContainer(containerId, trainingDetailInstance.getRemainingTime());
				if(CommonConstants.FAIL.intValue() == container.getCode()) {
					return createEnv(virtualTrainingDetailInstanceDto, answer, material, computingResource, virtualTrainingEnvironment,
							runTime, roleNames, trainingDetailInstance.getId());
				}
				trainingDetailInstance.setContainerId(container.getData().getId());
				trainingDetailInstance.setContainerStartTime(LocalDateTime.now());
				trainingDetailInstance.setContainerState(container.getData().getState());
				String containerIp = container.getData().getPrimaryIpAddress();
				trainingDetailInstance.setContainerIp(containerIp);
				saveOrUpdate(trainingDetailInstance);
				Map<String, Object> result = BeanUtil.beanToMap(trainingDetailInstance);
//				result.put("remainingTime", trainingDetailInstance.getRemainingTime());
				result.put("envType", virtualTrainingEnvironment.getEnvType());
				result.put("container", containerIp+":"+virtualTrainingEnvironment.getPort());
				result.put("token", virtualTrainingEnvironment.getToken());
				waitForEnvStart(containerIp, virtualTrainingEnvironment.getPort().toString(), virtualTrainingEnvironment.getToken(), virtualTrainingEnvironment.getEnvType());
				return Result.buildOkData(result, "启动环境成功！");
			}
			return createEnv(virtualTrainingDetailInstanceDto, answer, material, computingResource, virtualTrainingEnvironment,
					runTime, roleNames, trainingDetailInstance.getId());
		}
	}

	private Result createEnv(VirtualTrainingDetailInstanceDto virtualTrainingDetailInstanceDto, String answer,
			String material, String computingResource, TVirtualTrainingEnvironment virtualTrainingEnvironment, Integer runTime, String roleNames, String trainingDetailInstanceId) {
		ContainerDTO dto = new ContainerDTO();
		dto.setImageUuid(virtualTrainingEnvironment.getImage());
		if(StringUtil.isNotEmpty(computingResource)) {
			dto.setCpuCount(Integer.valueOf(computingResource.split(",")[0]));
			dto.setMemory(Integer.valueOf(computingResource.split(",")[1]));
		}
		dto.setRunTime(runTime);
		VirtualTrainingDetailInstance trainingDetailInstance = null;
		if(StringUtil.isEmpty(trainingDetailInstanceId)) {
			trainingDetailInstance = copyDtoToBean(virtualTrainingDetailInstanceDto);
		}else {
			trainingDetailInstance = getById(trainingDetailInstanceId);
		}
		List<String> dataVolumes = new ArrayList<>();
		String nfsDir = PropertiesUtils.getProperty("nfs.mount.dir");
		String srcDir = UUID.randomUUID().toString().replace("-", "");
		String materialDir = nfsDir+StringUtil.SLASH+srcDir+StringUtil.SLASH+"material";
		String answerDir = nfsDir+StringUtil.SLASH+srcDir+StringUtil.SLASH+"answer";
		if(StringUtil.isNotEmpty(material)) {
			if(StringUtil.isEmpty(trainingDetailInstanceId)) {
				int dirLastIndex = material.lastIndexOf(StringUtil.SLASH) + 1;
				String srcFile = ApplicationConfig.getUploadPath()+StringUtil.SLASH+StringUtil.subSuf(material, dirLastIndex);

				if(!FileUtil.createDirectory(materialDir)) {
					return Result.buildFail("创建素材目录失败！");
				}
				String targetFile = materialDir+StringUtil.SLASH+StringUtil.subSuf(material, dirLastIndex);
				if(targetFile.endsWith(".zip")){
					try {
						FileUtil.decompress(srcFile, materialDir);
					} catch (Exception e) {
						e.printStackTrace();
						return Result.buildFail("解压缩素材文件失败！");
					}
				}else{
					if(!FileUtil.copyFile(srcFile, targetFile)) {
						return Result.buildFail("复制素材文件失败！");
					}
				}
				trainingDetailInstance.setMaterial(materialDir);
			}
		}
		if(StringUtil.isEmpty(trainingDetailInstanceId)) {
			if(!FileUtil.createDirectory(answerDir)) {
				return Result.buildFail("创建答案目录失败！");
			}
			if(roleNames.contains("教师") && StringUtil.isNotEmpty(answer)) {
				int dirLastIndex = answer.lastIndexOf(StringUtil.SLASH) + 1;
				String srcFile = ApplicationConfig.getUploadPath()+StringUtil.SLASH+StringUtil.subSuf(answer, dirLastIndex);
				String targetFile = answerDir+StringUtil.SLASH+StringUtil.subSuf(answer, dirLastIndex);
				if(targetFile.endsWith(".zip")){
					try {
						FileUtil.decompress(srcFile, answerDir);
					} catch (Exception e) {
						e.printStackTrace();
						return Result.buildFail("解压缩答案文件失败！");
					}
				}else{
					if(!FileUtil.copyFile(srcFile, targetFile)) {
						return Result.buildFail("复制答案文件失败！");
					}
				}
			}
			trainingDetailInstance.setAnswer(answerDir);
		}else {
			String answer2 = trainingDetailInstance.getAnswer();
			if(StringUtil.isNotEmpty(answer2)){
				srcDir = answer2.substring(nfsDir.length()+1, answer2.indexOf(StringUtil.SLASH+"answer"));
			}else {
				if(!FileUtil.createDirectory(answerDir)) {
					return Result.buildFail("创建答案目录失败！");
				}
				trainingDetailInstance.setAnswer(answerDir);
			}
		}
		dataVolumes.add(srcDir+":"+virtualTrainingEnvironment.getDataDir());
		dto.setDataVolumes(dataVolumes );
		Result<Container> container = rancherService.createContainer(dto );
		if(CommonConstants.FAIL.intValue() == container.getCode()) {
			return Result.buildFail(container.getMessage());
		}

		trainingDetailInstance.setContainerId(container.getData().getId());
		trainingDetailInstance.setContainerStartTime(LocalDateTime.now());
		trainingDetailInstance.setContainerState(container.getData().getState());
		String containerIp = container.getData().getPrimaryIpAddress();
		trainingDetailInstance.setContainerIp(containerIp);
		saveOrUpdate(trainingDetailInstance);
		Map<String, Object> result = BeanUtil.beanToMap(trainingDetailInstance);
		result.put("envType", virtualTrainingEnvironment.getEnvType());
		result.put("container", containerIp+":"+virtualTrainingEnvironment.getPort());
		result.put("token", virtualTrainingEnvironment.getToken());
		waitForEnvStart(containerIp, virtualTrainingEnvironment.getPort().toString(), virtualTrainingEnvironment.getToken(), virtualTrainingEnvironment.getEnvType());
		return Result.buildOkData(result, "启动环境成功！");
	}

	private void waitForEnvStart(String containerIp, String containerPort, String token, String envType){
		switch (envType.trim()){
			case "1":
				QueryWrapper<Dict> qw = new QueryWrapper<>();
				qw.eq("code", "docker_image_url_desktop_websocket");
				Dict di = dictService.getOne(qw);
				if(ObjectUtil.isNotEmpty(di)){
					String url = di.getVal()+"?vncserver="+containerIp+"&vncport="+containerPort;
					final Charset charset = Charset.forName("utf-8");
					try {
						//创建Xnio实例，并构造XnioWorker
						final Xnio xnio = Xnio.getInstance();
						final XnioWorker worker = xnio.createWorker(OptionMap.EMPTY);
						ByteBufferPool pool = new DefaultByteBufferPool(true, 1024);
						IoFuture<WebSocketChannel> ioFuture = WebSocketClient.connectionBuilder(worker, pool, new URI(url)).connect();
						IoFuture.Status status = ioFuture.getStatus();
						log.info("桌面websocket代理连接测试url:"+url+"结果："+status.name());
						while (status.equals(IoFuture.Status.FAILED)){
							Thread.sleep(1000L);
							status = WebSocketClient.connectionBuilder(worker, pool, new URI(url)).connect().getStatus();
							log.info("桌面websocket代理连接测试url:"+url+"结果："+status.name());
						}
					}catch (Exception e){
						e.printStackTrace();
					}
				}
				break;
			case "2":
				qw = new QueryWrapper<>();
				qw.eq("code", "docker_image_url_web");
				di = dictService.getOne(qw);
				if(ObjectUtil.isNotEmpty(di)){
					String url = di.getVal()+"?container="+containerIp+":"+containerPort+"&token="+token;
					ClientHttpRequestFactory clientHttpRequestFactory = new SimpleClientHttpRequestFactory();
					try {
						ClientHttpResponse response = clientHttpRequestFactory.createRequest(new URI(url), HttpMethod.GET).execute();
						int status = response.getStatusCode().value();
						log.info("web代理连接测试url:"+url+"结果："+status);
						while (status != 200 && status != 302){
							try {
								Thread.sleep(1000L);
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
							status = clientHttpRequestFactory.createRequest(new URI(url), HttpMethod.GET).execute().getStatusCode().value();
							log.info("web代理连接测试url:"+url+"结果："+status);
						}
					}catch (Exception e){
						e.printStackTrace();
					}
				}
				break;
		}
	}

	@Override
	public Result stopVirtualEnv(VirtualTrainingDetailInstanceDto virtualTrainingDetailInstanceDto) {
		if(ObjectUtil.isEmpty(virtualTrainingDetailInstanceDto)
				|| StringUtil.isEmpty(virtualTrainingDetailInstanceDto.getId())) {
			return Result.buildFail("参数不能为空");
		}
		VirtualTrainingDetailInstance trainingDetailInstance = getById(virtualTrainingDetailInstanceDto.getId());
		String containerId = trainingDetailInstance.getContainerId();
		if(StringUtil.isNotEmpty(containerId)) {
			LocalDateTime containerStartTime = trainingDetailInstance.getContainerStartTime();
			long minutes = Duration.between(containerStartTime,LocalDateTime.now()).toMinutes();
			Result<Container> stopContainer = rancherService.stopContainer(containerId);
			if(CommonConstants.FAIL.intValue() == stopContainer.getCode()) {
				return Result.buildFail(stopContainer.getMessage());
			}
			Integer remainingTime = trainingDetailInstance.getRemainingTime();
			trainingDetailInstance.setRemainingTime(remainingTime.intValue()-Long.valueOf(minutes).intValue());
			trainingDetailInstance.setContainerState(stopContainer.getData().getState());
			saveOrUpdate(trainingDetailInstance);
		}
		return Result.buildOkData(trainingDetailInstance, "停止环境成功！");
	}

	@Override
	public Result deleteVirtualEnv(VirtualTrainingDetailInstanceDto virtualTrainingDetailInstanceDto) {
		if(ObjectUtil.isEmpty(virtualTrainingDetailInstanceDto)
				|| StringUtil.isEmpty(virtualTrainingDetailInstanceDto.getId())) {
			return Result.buildFail("参数不能为空");
		}
		VirtualTrainingDetailInstance trainingDetailInstance = getById(virtualTrainingDetailInstanceDto.getId());
		String containerId = trainingDetailInstance.getContainerId();
		if(StringUtil.isNotEmpty(containerId)) {
			LocalDateTime containerStartTime = trainingDetailInstance.getContainerStartTime();
			long minutes = Duration.between(containerStartTime,LocalDateTime.now()).toMinutes();
			Result deleteContainer = rancherService.deleteContainer(containerId);
			if(CommonConstants.FAIL.intValue() == deleteContainer.getCode()) {
				return Result.buildFail(deleteContainer.getMessage());
			}
			Integer remainingTime = trainingDetailInstance.getRemainingTime();
			trainingDetailInstance.setRemainingTime(remainingTime.intValue()-Long.valueOf(minutes).intValue());
			trainingDetailInstance.setContainerState("removed");
			saveOrUpdate(trainingDetailInstance);
		}
		return Result.buildOkData(trainingDetailInstance, "删除环境成功！");
	}

	@Override
	public Result delayed(String trainingDetailInstanceId, Integer time) {
		VirtualTrainingDetailInstance trainingDetailInstance = getById(trainingDetailInstanceId);
		LocalDateTime containerStartTime = trainingDetailInstance.getContainerStartTime();
		LocalDateTime now = LocalDateTime.now();
		long minutes = Duration.between(containerStartTime,now).toMinutes();
		Integer remainingTime = trainingDetailInstance.getRemainingTime();
		int remainTime = remainingTime.intValue()-Long.valueOf(minutes).intValue();
		if(remainTime > 5) {
			return Result.buildFailData(trainingDetailInstance, "请剩余时间不足5分钟再延时！");
		}
		trainingDetailInstance.setContainerStartTime(now);
		trainingDetailInstance.setRemainingTime(remainTime+time.intValue());
		saveOrUpdate(trainingDetailInstance);
		return Result.buildOkData(trainingDetailInstance, "延时成功！");
	}

	@Override
	public List<VirtualTrainingDetailInstance> findTimeOutTrainingDetailInstance() {
		return repository.findTimeOutTrainingDetailInstance();
	}

}
